
在現代雲原生應用程式中,跨多個 Kubernetes 叢集部署和管理服務變得越來越普遍。Google Kubernetes Engine (GKE) 提供了強大的功能來簡化這個過程,其中 GKE Fleet 和 Multi-cluster Services (MCS) 是兩個關鍵技術。
本文將深入探討如何利用 Shared VPC 在 GKE Fleet 中設置 MCS,實現跨不同區域叢集的服務發現和流量管理。此架構不僅提升了應用程式的可用性和容錯能力,也簡化了網路管理的複雜度,讓您可以更專注於應用程式的開發和部署。 無論您是 Kubernetes 新手還是經驗豐富的用戶,本文都將提供您在 GKE 環境中建構和管理多叢集服務的實用知識。
GKE 多叢集服務 (Multi-Cluster Services,MCS) 允許您在多個 Google Kubernetes Engine (GKE) 叢集上部署和管理應用程式。它簡化了跨多個叢集的操作,例如流量分割、故障轉移和服務探索,讓您可以更輕鬆地建構和管理高度可用且可擴展的應用程式。
以下是 GKE MCS 的主要功能和優點:
接續第 Day2, Day3,會使用 Day2 創建的 Shared VPC 網路及Day3 的 GKE Terraform,需要再創建一個 GKE 叢集在 US-West4 地區。
GKE Fleet 網路架構表如下:

將讀者的設定取代以下變數內容:
SHARED_VPC_HOST_PROJ:共用 VPC 宿主專案的 IDFLEET_HOST_PROJ_NUMBER:艦隊宿主專案的編號,不是專案 ID 唷!FLEET_HOST_PROJ:集群專案的ID。在艦隊宿主專案中啟用 GKE Hub, Traffic Director、Resource Manager 、Anthos、Cloud DNS 和 Multi-cluster Service Discovery API
$ gcloud services enable gkehub.googleapis.com \
		anthos.googleapis.com \
		trafficdirector.googleapis.com \
    cloudresourcemanager.googleapis.com \
    multiclusterservicediscovery.googleapis.com \
    dns.googleapis.com \
    --project $FLEET_HOST_PROJ
在 SHARED VPC 專案下開啟 Cloud DNS API
$ gcloud services enable dns.googleapis.com \
    --project $SHARED_VPC_HOST_PROJ
$ gcloud container fleet multi-cluster-services enable \
    --project $FLEET_HOST_PROJ
##輸出
Waiting for Feature Multi-cluster Services to be created...done. 
如果在艦隊宿主專案中啟用多集群服務,則系統會創建以下服務帳號或者確保以下服務帳號存在 service-$FLEET_HOST_PROJ_NUMBER@gcp-sa-mcsd.iam.gserviceaccount.com 
使用 GKE Fleet 需要開啟 GKE Enterprise 版本,如下圖所示這樣就代表有開啟了。
運行以下命令創建空艦隊
$ gcloud alpha container fleet create --display-name=$NAME --project=$FLEET_HOST_PROJECT

創建完成後,可以在 Fleet Host Project(艦隊宿主專案)下的的 GKE 頁面會看到 Fleet(機群)會顯示出來,而 Cluster(叢集)頁面也會出現 REGISTER(註冊)的選項。
將第一個集群註冊到艦隊。 -gke-cluster 標誌可用於此命令,因為第一個集群與註冊它的艦隊位於同一專案中。
$ gcloud container fleet memberships $叢集名稱_1 \
    --project $FLEET所在專案ID \
    --enable-workload-identity \
    --gke-cluster=$地區/$叢集名稱_1
取代以下內容:
$叢集名稱_1:此集群在此艦隊中的唯一標識符。 通常使用第一個要註冊的 GKE 集群名稱。$FLEET所在專案ID:艦隊宿主專案的ID。$地區:對於可用區級集群,這是包含集群的 Compute Engine 可用區; 對於區域級集群,這是包含集群的 Compute Engine 區域。$叢集名稱_2:第一個要註冊的叢集的名稱。將第二個集群註冊到艦隊宿主專案。 -gke-cluster 標誌可用於此命令,因為第二個集群也位於艦隊宿主專案中。
$ gcloud container fleet memberships $叢集名稱_2 \
    --project $FLEET所在專案ID \
    --enable-workload-identity \
    --gke-cluster=$地區/$叢集名稱_2
取代以下內容:
$叢集名稱_2:此集群在此艦隊中的唯一標識符。 通常使用第二個要註冊的 GKE 集群名稱。$FLEET所在專案ID:艦隊宿主專案的ID。$地區:對於可用區級集群,這是包含集群的 Compute Engine 可用區; 對於區域級集群,這是包含集群的 Compute Engine 區域。$叢集名稱_2:第二個要註冊的叢集的名稱。點擊下圖中的註冊,將集群註冊到當前專案中的 Fleet(機群)中

註冊完成後會如下圖顯示叢集所在的機群

再創建一個在 us-west4 的 GKE 叢集,並註冊到此專案中的 Fleet(機群)

創建 IAM 綁定,向艦隊宿主專案 MCS 服務帳號授予共用 VPC 宿主專案的 MCS Service Agent 角色:
$ gcloud projects add-iam-policy-binding $SHARED_VPC_HOST_PROJ \
    --member "serviceAccount:service-$FLEET_HOST_PROJ_NUMBER@gcp-sa-mcsd.iam.gserviceaccount.com" \
    --role roles/multiclusterservicediscovery.serviceAgent
由於此場景使用適用於 GKE 的工作負載身份聯合,因此艦隊宿主專案的 MCS 導入程式 GKE 服務帳號需要自己專案的 Network User 角色。
創建 IAM 綁定,向艦隊宿主專案 MCS 服務帳號授予自己專案和 Shared VPC 專案的 Network User 角色:
$ gcloud projects add-iam-policy-binding $FLEET_HOST_PROJ \
    --member "serviceAccount:$FLEET_HOST_PROJ.svc.id.goog[gke-mcs/gke-mcs-importer]" \
    --role roles/compute.networkViewer
    
$ gcloud projects add-iam-policy-binding $SHARED_VPC_HOST_PROJ \
    --member "serviceAccount:$FLEET_HOST_PROJ.svc.id.goog[gke-mcs/gke-mcs-importer]" \
    --role roles/compute.networkViewer
可由以下指令驗證是否已啟用 MCS。
$ gcloud container fleet multi-cluster-services describe --project ithome-202409-demo-2
createTime: '2024-10-11T08:57:43.606552889Z'
membershipStates:
  projects/407205409614/locations/us-central1/memberships/demo2-cluster:
    state:
      code: OK
      description: Firewall successfully updated
      updateTime: '2024-10-13T07:31:59.227720484Z'
  projects/407205409614/locations/us-west4/memberships/demo2-us-west4-fleet-cluster:
    state:
      code: OK
      description: Firewall successfully updated
      updateTime: '2024-10-13T07:31:58.574210973Z'
name: projects/ithome-202409-demo-2/locations/global/features/multiclusterservicediscovery
resourceState:
  state: ACTIVE
spec: {}
updateTime: '2024-10-11T08:57:46.039837503Z'
由輸出可得知兩個集群都已經正常啟用 MCS 了。
同樣 GKE Fleet 的兩個叢集內會創建gke-mcs NameSpace,並在此 Namespace 下創建 Deployment 及 ServiceAccount,可以到此 Fleet(機群) 下的所有叢集中查看,
$ kubectl get deployment -n gke-mcs  
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
gke-mcs-importer   1/1     1            1           1m
$ kubectl get sa -n gke-mcs          
NAME               SECRETS   AGE
default            0         1m
gke-mcs-importer   0         1m
下表為本次實驗環境的 GKE Cluster 所有網路相關設定。
| GKE Cluster Name | Region | Node IP CIDR | Pod CIDR | Service CIDR | 
|---|---|---|---|---|
| demo2-cluster | us-central1 | 10.120.64.0/20 | 10.120.80.0/20 | 10.120.96.0/20 | 
| demo2-us-west4-fleet-cluster | us-west4 | 10.123.64.0/20 | 10.123.80.0/20 | 10.123.96.0/20 | 
以下使用的鏡像 a51907/display-podname-image:2024 是筆者自己製作可以在前端網頁顯示 Pod 中的環境變數來知道自己的 Pod Name 及 Service Name。
在us-central1這個地區的集群創建以下 Deployment,有特別標記名稱為 us-central1。
kubectl apply -f us-central1-deployment.yaml
# us-central1-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: us-central1-deployment
  namespace: mcs-test
spec:
  replicas: 2
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
        - name: display-podname-image
          image: a51907/display-podname-image:2024
          ports:
          - containerPort: 80
在us-west4這個地區的集群創建以下 Deployment,有特別標記名稱為 us-west4。
kubectl apply -f us-west4-deployment.yaml
# us-west4-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: us-west4-deployment
  namespace: mcs-test
spec:
  replicas: 2
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
        - name: display-podname-image
          image: a51907/display-podname-image:2024
          ports:
          - containerPort: 80
在兩個不同地區的不同集群(us-central1及us-west4)都創建上述兩個  Deployment 的 Service 及 ServiceExport
kubectl apply -f mcs-test.yaml
# mcs-test.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-service
  namespace: mcs-test
spec:
  selector:
    app: my-app
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  type: ClusterIP
---
# serviceexport.yaml
kind: ServiceExport
apiVersion: net.gke.io/v1
metadata:
 name: my-service
 namespace: mcs-test
等待五分鐘後,系統將會於兩個集群都自動創建 ServiceImport 物件。以下 Tsh 的指令是 Day15 Teleport 介紹,可以快速切換當前的集群。
進入 us-central1 的叢集 demo2-cluster
$ tsh kube login demo2-cluster
$ kubectl get ServiceImport -n mcs-test
NAME         TYPE           IP                   AGE
my-service   ClusterSetIP   ["10.120.102.128"]   8m20s
kubectl get Service -n mcs-test
NAME                 TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
gke-mcs-8b6i2bo58n   ClusterIP   10.120.102.128   <none>        80/TCP    53m
my-service           ClusterIP   10.120.98.2      <none>        80/TCP    57m
$ kubectl get endpoints gke-mcs-8b6i2bo58n -n mcs-test
NAME                 ENDPOINTS                                                       AGE
gke-mcs-8b6i2bo58n   10.120.80.193:80,10.120.80.71:80,10.123.80.152:80 + 1 more...   54m
進入 us-west4 的叢集 demo2-us-west4-fleet-cluster
$ tsh kube login demo2-us-west4-fleet-cluster
$ kubectl get ServiceImport -n mcs-test
NAME         TYPE           IP                   AGE
my-service   ClusterSetIP   ["10.123.111.156"]   9m
$ kubectl get Service -n mcs-test
NAME                 TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
gke-mcs-8b6i2bo58n   ClusterIP   10.123.111.156   <none>        80/TCP    52m
my-service           ClusterIP   10.123.97.28     <none>        80/TCP    25m
$ kubectl get endpoints gke-mcs-8b6i2bo58n -n mcs-test
NAME                 ENDPOINTS                                                       AGE
gke-mcs-8b6i2bo58n   10.120.80.193:80,10.120.80.71:80,10.123.80.152:80 + 1 more...   54m
可以發現 ServiceImport 和系統自動創建的 gke-mcs Service 會是同一個 IP,我們去搜索 MCS Service 背後對應的 Endpoint,會發現對應到不同集群中的 Pod IP。
讀者如果設定完後,ServiceImport, Service, Endpoints輸出都如上述所示,恭喜!那就代表設定無誤。此時 GKE Fleet 架構如下圖:

接下來,在兩個不同地區的不同集群(us-central1及us-west4)都啟動 radial/busyboxplus:curl 的 pod,待會要使用其來進行 Curl 的指令來確定剛剛創建的 MCS Service 可以正確轉發到不同的集群內
$ kubectl run test-curl --image=radial/busyboxplus:curl -n mcs-test  --command -- /bin/sh -c "while true; do sleep 360000; done"
進入上個步驟創建的 Pod 中,請求 K8s 內部的 Service DNS 解析(gke-mcs-8b6i2bo58n.mcs-test.svc.cluster.local:80)
$ exec kubectl exec -i -t -n mcs-test test-curl -c test-curl -- sh -c "clear; (bash || ash || sh)"
[root@test-curl:/]$ curl http://gke-mcs-8b6i2bo58n.mcs-test.svc.cluster.local:80
可以看到請求到不同地區的 GKE 叢集及不同的 Pod 之中。

由於今年 AI 熱潮導致 GCP 特定區域的 GPU 資源經常短缺,筆者在部署 GPU 叢集時,經常遇到配額不足的問題。由於 GKE 叢集區域固定,若單一節點池(Node Pool)需更換區域,則必須重建整個叢集。為了避免這種情況,並確保 GPU 資源的彈性調度,我們選擇採用 GKE Fleet 的多叢集架構。
這系列的鐵人賽文章也到了第 29 天了,由於篇幅有限,只能帶領讀者體驗 GKE Fleet 的入門,更進階的課題還有 Multi-Cluster Ingress, Multi-Cluster Gateway 這兩項是 Day7 和 Day10 的延伸,以及 Anthos 混合雲和多雲應用程式現代化平台,將不同雲供應商的 K8s Cluster 彼此串接製作成集群艦隊唷!